.. _Дополнительно о функциях:

Дополнительно о функциях
========================

До сих пор функции рассматривались в следующих главах книги:
* :ref:`Функции<глава_32_start>`
* :ref:`Параметры функций<глава_35_start>`
* :ref:`Перегрузка функций<глава_49_start>`
* :ref:`Указатели функций, делегаты и ламбды <глава_72_start>`
В этой главе мы рассмотрим дополнительные возможности функций.

Атрибуты типа возврата
----------------------

Функции могут быть помечены как auto, ref, inout и auto ref. Эти атрибуты специфицируют тип возвращаемого значения функций

.. index::
    auto function
    ref function

**аuto функции**

Типы возврата аuto функций указывать не нужно:

.. code-block:: d

    auto add(int first, double second)
    {
        double result = first + second;
        return result;
    }

Тип возврата выводится компилятором из выражения return. Поскольку
тип result - double, то и тип возвращаемого выражения add() - double.
Если выражений возврата несколько, то тип возврата функции
функции является их общим типом. (Общий тип мы рассматривали в главе Тернарный
Оператор ?: в главе (:ref:`Тернарный опрератор<глава_23_start>`).) Например, поскольку общим типом для int и
double является double, то тип возврата следующей функции auto также является double

.. code-block:: d

    auto func(int i)
    {
        if (i < 0)
        {
            return i; // returns 'int' here
        }
        return i * 1.5; // returns 'double' here
    }

    void main()
    {
        // The return type of the function is 'double'
        auto result = func(42);
        static assert(is(typeof(result) == double));
    }

**ref функции**

Обычно выражение, возвращаемое функцией, копируется в вызывающий контекст
ref указывает, что выражение должно быть возвращено по ссылке вместо этого.
Например, следующая функция возвращает больший из двух параметров:

.. code-block:: d

    int greater(int first, int second)
    {
        return (first > second) ? first : second;
    }

Обычно копируются как параметры, так и возвращаемое значение этой функции:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int a = 1;
        int b = 2;
        int result = greater(a, b);
        result += 10; // ← neither a nor b changes
        writefln("a: %s, b: %s, result: %s", a, b, result);
    }

Поскольку возвращаемое значение greater() копируется в result, добавление к result
влияет только на эту переменную; ни a, ни b не изменяются:

.. code-block:: console

    a: 1, b: 2, result: 12

Параметры ref передаются по ссылкам, а не копируются. То же самое
ключевое слово оказывает такое же влияние на возвращаемые значения:

.. code-block:: d

    ref int greater(ref int first, ref int second)
    {
        return (first > second) ? first : second;
    }

На этот раз возвращаемая ссылка будет псевдонимом одного из аргументов, и
мутация возвращенной ссылки изменит либо a, либо b:

.. code-block:: d

    int a = 1;
    int b = 2;
    greater(a, b) += 10; // ← either a or b changes
    writefln("a: %s, b: %s", a, b);

Обратите внимание, что возвращаемая ссылка инкрементируется напрямую. В результате больший
из двух аргументов изменяется:

.. code-block:: console

    a: 1, b: 12

**Для локальной ссылки требуется указатель:** Важным моментом является то, что хотя
возвращаемый тип обозначен как ref, a и b все равно не изменятся, если возвращаемое значение
было присвоено локальной переменной:

.. code-block:: d

    int result = greater(a, b);
    result += 10;

Хотя greater() возвращает ссылку на a или b, эта ссылка копируется в
локальную переменную result, и снова ни a, ни b не меняются.

.. code-block:: d

    a: 1, b: 2, result: 12

**Невозможно вернуть ссылку на локальную переменную:** Возвращаемое значение ref является
псевдонимом одного из аргументов, которые начинают свою жизнь еще до вызова функции
вызывается. Это значит, что независимо от того, возвращается ли ссылка на a или b, возвращаемая ссылка
возвращенная ссылка ссылается на переменную, которая все еще жива.
И наоборот, невозможно вернуть ссылку на переменную, которая не будет
не будет жива после выхода из функции:

.. code-block:: d

    ref string parenthesized(string phrase)
    {
        string result = '(' ~ phrase ~ ')';
        return result; // ← ошибка компиляции
    } // ← the lifetime of result ends here

Время жизни локального результата заканчивается после выхода из функции. По этой причине
невозможно вернуть ссылку на эту переменную:

.. code-block:: console

    Error: escaping reference to local variable result

**Функции auto ref**

auto ref помогает при работе с функциями, подобными parenthesized(). Как и в случае с auto, тип
тип возвращаемой функции auto ref определяется компилятором. Кроме того, если
возвращаемое выражение может быть ссылкой, то переменная возвращается по ссылке
а не копируется.
parenthesized() может быть скомпилирована, если возвращаемый тип - auto ref:


.. code-block:: d

    auto ref string parenthesized(string phrase)
    {
        string result = '(' ~ phrase ~ ')';
        return result; // ← compiles
    }

Самый первый оператор возврата функции определяет, возвращает ли функция
возвращает копию или ссылку.
auto ref более полезен в шаблонах функций, где параметры шаблона
могут быть ссылками или копиями в зависимости от контекста.

**функции inout**

Ключевое слово inout используется в функциях типа параметр и возврат. Оно работает
как шаблон для const, immutable и mutable.
Перепишем предыдущую функцию как принимающую строку (т. е. immutable(char)[])
и возвращающую строку:

.. code-block:: d

    string parenthesized(string phrase)
    {
        return '(' ~ phrase ~ ')';
    }
    // ...
    writeln(parenthesized("hello"))

Как и ожидалось, код работает с этим строковым аргументом:

.. code-block:: console

    (hello)

Однако, поскольку она работает только с неизменяемыми строками, функцию можно рассматривать как
менее полезной, чем могла бы быть:

.. code-block:: d

    char[] m; // has mutable elements
    m ~= "hello";
    writeln(parenthesized(m)); // ← ошибка компиляции

.. code-block:: d

    Error: function deneme.parenthesized (string phrase)
    is not callable using argument types (char[])

Это же ограничение применимо и к строкам const(char)[].
Одним из решений этой проблемы является перегрузка функции для const и
mutablestrings:

.. code-block:: d

    char[] parenthesized(char[] phrase)
    {
        return '(' ~ phrase ~ ')';
    }

    const(char)[] parenthesized(const(char)[] phrase)
    {
        return '(' ~ phrase ~ ')';
    }


Такой дизайн был бы менее чем идеальным из-за очевидного дублирования кода.
Другим решением было бы определить функцию как шаблон:

.. code-block::

    T parenthesized(T)(T phrase)
    {
        return '(' ~ phrase ~ ')';
    }

Хотя это могло бы сработать, на этот раз это может быть расценено как слишком гибкое и
потенциально требующее ограничений шаблона.
in out очень похоже на шаблонное решение. Разница в том, что
из параметра выводится не весь тип, а только атрибут изменяемости:

.. code-block:: d

    inout(char)[] parenthesized(inout(char)[] phrase)
    {
        return '(' ~ phrase ~ ')';
    }

inout передает выведенный атрибут мутабельности возвращаемому типу.
Когда функция вызывается с char[], она компилируется так, как если бы inout не был
как будто inout вообще не указан. С другой стороны, при вызове с immutable(char)[] или
const(char)[], inout означает immutable или const, соответственно.
Следующий код демонстрирует это, выводя тип возвращаемого
выражения:

.. code-block:: d

    char[] m;
    writeln(typeof(parenthesized(m)).stringof);
    const(char)[] c;
    writeln(typeof(parenthesized(c)).stringof);
    immutable(char)[] i;
    writeln(typeof(parenthesized(i)).stringof);

Вывод:

.. code-block:: console

    char[]
    const(char)[]
    string

Атрибуты определяющие поведение
-------------------------------

pure, nothrow и @nogc относятся к поведению функций.

.. index::
    чистые функции
    pure function

**чистые функции**

Как мы уже видели в главе :ref:`Функции<глава_32_start>`, функции могут выдавать возвращаемые
значения и побочные эффекты. Когда это возможно, следует отдавать предпочтение возвращаемым значениям, а не
потому что функции, не имеющие побочных эффектов, легче понять.
что, в свою очередь, способствует корректности и сопровождаемости программы.
Аналогичным понятием является чистота функции. Чистота определяется в D иначе
чем в большинстве других языков программирования: В D функция, которая не обращается к
мутабельным глобальным или статическим состояниям, является чистой. (Поскольку входные и выходные потоки
рассматриваются как изменяемое глобальное состояние, чистые функции не могут выполнять операции ввода или
операции ввода или вывода).
Другими словами, функция является чистой, если она создает свое возвращаемое значение и побочные эффекты
только путем обращения к своим параметрам, локальным переменным и неизменяемому глобальному состоянию.
Важным аспектом чистоты в D является то, что чистые функции могут изменять свои
параметры.
Кроме того, следующие операции, изменяющие глобальное состояние
явно разрешены в чистых функциях:
* Выделить память под новое выражение
* Завершение работы программы
* Получить доступ к флагам обработки с плавающей точкой
* Выбрасывать исключения
Ключевое слово pure указывает, что функция должна вести себя в соответствии с этими
и компилятор гарантирует, что она будет вести себя именно так.
Естественно, поскольку нечистые функции не предоставляют таких же гарантий, чистая
функция не может вызывать нечистые функции.
Следующая программа демонстрирует некоторые операции, которые может и не может выполнять чистая функция
функция может и не может выполнять:

.. code-block:: d

    import std.stdio;
    import std.exception;

    int mutableGlobal;
    const int constGlobal;
    immutable int immutableGlobal;
    void impureFunction()
    {
    }

    int pureFunction(ref int i, int[] slice) pure
    {
        // Can throw exceptions:
        enforce(slice.length >= 1);
        // Can mutate its parameters:
        i = 42;
        slice[0] = 43;
        // Can access immutable global state:
        i = constGlobal;
        i = immutableGlobal;
        // Can use the new expression:
        auto p = new int;
        // Cannot access mutable global state:
        i = mutableGlobal; // ← compilation ERROR
        // Cannot perform input and output operations:
        writeln(i); // ← compilation ERROR
        static int mutableStatic;
        // Cannot access mutable static state:
        i = mutableStatic; // ← compilation ERROR
        // Cannot call impure functions:
        impureFunction(); // ← compilation ERROR
        return 0;
    }

    void main()
    {
        int i;
        int[] slice = [1];
        pureFunction(i, slice);
    }

Хотя это разрешено, некоторые чистые функции не изменяют свои
параметры. Исходя из правил чистоты, единственным наблюдаемым эффектом такой
функции будет ее возвращаемое значение. Кроме того, поскольку функция не может получить доступ
ни к какому мутируемому глобальному состоянию, возвращаемое значение будет одним и тем же для данного набора
аргументов, независимо от того, когда и сколько раз функция будет вызвана во время
во время выполнения программы. Этот факт дает возможности для оптимизации как компилятору, так и
программисту возможности для оптимизации. Например, вместо того чтобы вызывать
функцию второй раз для заданного набора аргументов, ее возвращаемое значение после первого
может быть кэшировано и использовано вместо повторного вызова функции.
Поскольку точный код, генерируемый для инстанцирования шаблона, зависит
от фактических аргументов шаблона, чистота сгенерированного кода зависит
также зависит от аргументов. По этой причине чистота шаблона определяется
компилятором по сгенерированному коду. (Ключевое слово pure все еще может быть указано
программист.) Аналогичным образом определяется чистота автофункции.
В качестве простого примера, поскольку следующий шаблон функции будет нечистым
когда N равно нулю, то невозможно вызвать templ!0() из чистой функции:

.. code-block:: d

    import std.stdio;

    // This template is impure when N is zero
    void templ(size_t N)()
    {
        static if (N == 0)
        {
            // Prints when N is zero:
            writeln("zero");
        }
    }

    void foo() pure
    {
        templ!0(); // ← ошибка компиляции
    }

    void main()
    {
        foo();
    }

Компилятор делает вывод, что 0 инстанция шаблона нечистая, и отвергает
вызов его из чистой функции foo():abbr:

.. code-block:: console

    Error: pure function 'deneme.foo' cannot call impure function 'deneme.templ!0.templ

Однако, поскольку экземпляром шаблона для значений, отличных от нуля, является
чистая функция, программа может быть скомпилирована для таких значений:

.. code-block:: d

    void foo() pure
    {
        templ!1(); // ← compiles
    }

Ранее мы видели, что функции ввода и вывода, такие как writeln()
нельзя использовать в чистых функциях, потому что они обращаются к глобальному состоянию. Иногда
такие ограничения слишком строги, например, когда нужно напечатать сообщение
во время отладки. По этой причине правила чистоты смягчаются для
кода, помеченного как отладочный:

.. code-block:: d

    import std.stdio;

    debug size_t fooCounter;
    void foo(int i) pure
    {
        debug ++fooCounter;
        if (i == 0)
        {
            debug writeln("i is zero");
            i = 42;
        }
        // ...
    }

    void main()
    {
        foreach (i; 0 .. 100)
        {
            if ((i % 10) == 0)
            {
                foo(i);
            }
        }
        debug writefln("foo is called %s times", fooCounter);
    }

Приведенная выше чистая функция изменяет глобальное состояние программы, модифицируя
глобальную переменную и печатает сообщение. Несмотря на эти нечистые операции, она все еще
может быть скомпилирована, поскольку эти операции помечены как отладочные.
*Примечание:Помните, что эти операторы включаются в программу только в том случае, если программа
программа скомпилирована с ключом командной строки -debug.*
Функции-члены также могут быть помечены как чистые. Подклассы могут переопределять
нечистые функции как чистые, но обратное не допускается:

.. code-block:: d

    interface Iface
    {
        void foo() pure; // Subclasses must define foo as pure.
        void bar(); // Subclasses may define bar as pure.
    }

    class Class : Iface
    {
        void foo() pure
        { // Required to be pure
            // ...
        }

        void bar() pure
        { // pure although not required
            // ...
        }
    }

Делегаты и анонимные функции также могут быть чистыми. Аналогично шаблонам,
является ли функция, литерал делегата или автофункция чистой, определяется компилятором.
компилятор:

.. code-block:: d

    import std.stdio;

    void foo(int delegate(double) pure dg)
    {
        int i = dg(1.5);
    }

    void main()
    {
        foo(a => 42); // ← компилтруется
        foo((a) { // ← ошибка компиляции
            writeln("hello");
            return 42;
        });
    }

foo() выше требует, чтобы его параметр был чистым делегатом. Компилятор считает.
что лямбда a => 42 является чистой, и разрешает ее в качестве аргумента для foo().
Однако, поскольку другой делегат является нечистым, он не может быть передан в foo():

.. code-block:: console

    Error: function deneme.foo (int delegate(double) pure dg) is not callable using argument types (void)

Одно из преимуществ чистых функций заключается в том, что возвращаемые ими значения можно использовать для инициализации
неизменяемых переменных. Хотя массив, созданный функцией makeNumbers() ниже, является
мутабельным, его элементы не могут быть изменены никаким кодом за пределами
этой функции. По этой причине инициализация работает.

.. code-block:: d

    int[] makeNumbers() pure
    {
        int[] result;
        result ~= 42;
        return result;
    }

    void main()
    {
        immutable array = makeNumbers();
    }

.. index::
    nothrow

**nothrow functions**

Мы познакомились с механизмом исключений в главе :ref:`Исключения<глава_39_start>`
Хорошей практикой для функций будет документирование типов исключений
которые они могут отбрасывать при определенных условиях ошибки. Однако, как правило,
вызывающим пользователям следует считать, что любая функция может выбросить любое исключение.
Иногда важнее знать, что функция не выбрасывает никаких
исключения вообще. Например, некоторые алгоритмы могут воспользоваться тем фактом, что
некоторые из их шагов не могут быть прерваны исключением.
nothrow гарантирует, что функция не выбрасывает никаких исключений

.. code-block:: d

    int add(int lhs, int rhs) nothrow
    {
    // ...
    }

*Примечание:Помните, что не рекомендуется перехватывать ошибку (catch Error) и ее базовый класс
Throwable. Под "любым исключением" здесь подразумевается "любое исключение, которое определено
в иерархии Exception". Функция Anothrow все равно может испускать исключения, которые
в иерархии Error, которая представляет собой неустранимые условия ошибки, которые должны
исключить продолжение выполнения программы.*
Такая функция не может ни сама выбросить исключение, ни вызвать функцию
которая может выбросить исключение:

.. code-block:: d

    int add(int lhs, int rhs) nothrow
    {
        writeln("adding"); // ← ошибка компиляции
        return lhs + rhs;
    }

Компилятор отвергает код, поскольку add() нарушает гарантию no-throw:

.. code-block:: console

    Error: function 'deneme.add' is nothrow yet may throw

Это происходит потому, что writeln не является (и не может являться) функцией nothrow.
Компилятор может сделать вывод, что функция никогда не может выдать исключение. Приведенная на
следующая реализация функции add() является nothrow, потому что компилятору очевидно.
компилятору, что блок try-catch предотвращает выход исключения из функции
функции:

.. code-block:: d

    int add(int lhs, int rhs) nothrow
    {
        int result;
        try
        {
            writeln("adding"); // ← compiles
            result = lhs + rhs;
        }
        catch (Exception error)
        { // catches all exceptions
            // ...
        }
        return result;
    }

Как уже говорилось выше, nothrow не включает исключения, которые находятся в иерархии
иерархии Error. Например, хотя обращение к элементу массива с помощью []
может привести к ошибке RangeError, следующая функция все равно может быть определена как nothrow:

.. code-block:: d

    int foo(int[] arr, size_t i) nothrow
    {
        return 10 * arr[i];
    }

.. index::
    @nogc

Как и в случае с чистотой, компилятор автоматически определяет, является ли шаблон, делегат,
или анонимная функция не является "nothrow".

**Функции @nogc**

D - это язык с функцией сборки мусора. Многие структуры данных и алгоритмы в большинстве программ на D
используют преимущества динамических блоков памяти, которыми управляет
сборщиком мусора (GC). Такие блоки памяти снова освобождаются GC с помощью алгоритма, называемого сборкой мусора.
Алгоритм называется сборкой мусора.
Некоторые часто используемые операции D также используют преимущества GC. Например,
например, элементы массивов живут в динамических блоках памяти:

.. code-block:: d

    // A function that takes advantage of the GC indirectly
    int[] append(int[] slice)
    {
        slice ~= 42;
        return slice;
    }

Если фрагмент не имеет достаточной емкости, оператор ~=, приведенный выше, выделяет новый
блок памяти из кучи (оригинал говорит запросит память у GC - что-то не то).
Хотя GC представляет собой значительное удобство для структур данных и
алгоритмов, выделение памяти и сборка мусора - дорогостоящие операции, которые
которые делают выполнение некоторых программ заметно медленным.
@nogc означает, что функция не может использовать GC прямо или косвенно:

.. code-block:: d

    void foo() @nogc
    {
    // ...
    }

Компилятор гарантирует, что функция @nogc не включает в себя операции GC.
Например, следующая функция не может вызвать вышеупомянутую append(), которая не
обеспечивает гарантию @nogc:

.. code-block::

    void foo() @nogc
    {
        int[] slice;
        // ...
        append(slice); // ← compilation ERROR
    }

.. code-block:: console

    Error: @nogc function 'deneme.foo' cannot call non-@nogc function 'deneme.append'

Атрибуты безопасности кода
--------------------------

Атрибуты @safe, @trusted и @system указывают на безопасность кода, которую обеспечивает функция.
Как и в случае с чистотой, компилятор определяет уровень безопасности шаблонов, делегатов,
анонимных функций и автофункций.

.. index::
    @safe

**Функции @safe**

Существует класс ошибок программирования связан с повреждением данных в непренадлежащих программе блоках памяти
памяти путем непреднамеренной записи в эти места. Такие ошибки в основном связаны с
ошибок при использовании указателей и приведении типов.
Функции отмеченные атрибутом @safe гарантируют, что они не содержат никаких операций, которые могут
испортить память. Компилятор не допускает в @safe следующие операции
функциях:

* Указатели не могут быть преобразованы в другие типы указателей, кроме void*.
* Выражение, не являющееся указателем, не может быть преобразовано в значение указателя.
* Значения указателей не могут быть изменены (нет арифметики указателей; однако присвоение указателя
  указателя другому указателю того же типа безопасно).
* Нельзя использовать объединения, содержащие члены-указатели или члены-ссылки.
* Функции, помеченные как @system, не могут быть вызваны.
* Исключения, не являющиеся потомками Exception, не могут быть пойманы.
* Невозможно использовать встроенный ассемблер.
* Мутабельные переменные не могут быть приведены к мутабельным.
* Неизменяемые переменные не могут быть приведены к изменяемым.
* Локальные переменные потока не могут быть приведены к общим.
* Общие переменные не могут быть приведены к потоково-локальным.
* Адреса переменных function-local не могут быть взяты.
* __gshared переменные не могут быть доступны

.. index::
    @trusted
    @system

**@trusted функции**

Некоторые функции на самом деле могут быть безопасными, но по разным причинам не могут быть помечены как @safe.
причинам. Например, функция может вызывать библиотеку, написанную на языке C, где нет
а в этом языке нет поддержки безопасности.
Некоторые другие функции могут на самом деле выполнять операции, которые не допускаются в
@safe, но могут быть хорошо протестированы и считаться корректными.
@trusted - это атрибут, который сообщает компилятору, что хотя функция
функция не может быть помечена как @safe, считать ее безопасной. Компилятор доверяет
программисту и обращается с @trusted кодом так, как будто он безопасен. Например, он позволяет @safe код вызывать @trusted код

**@system функции**

Любая функция, не помеченная как @safe или @trusted, считается @system,
что является атрибутом безопасности по умолчанию.

Выполнение функций во время компиляции (CTFE)
----------------------------------------------

Во многих языках программирования вычисления, выполняемые во время компиляции
во время компиляции, очень ограничены. Такие вычисления обычно просты, как вычисление
длины массива фиксированной длины или простые арифметические операции:
writeln(1 + 2);
Приведенное выше выражение 1 + 2 компилируется так, как если бы оно было записано как 3; во время выполнения не происходит никаких
вычислений во время выполнения.
В D есть CTFE, которая позволяет выполнять любую функцию во время компиляции, если только это возможно.
пока это возможно.
Рассмотрим следующую программу, которая выводит на экран меню:

.. code-block:: d

    import std.stdio;
    import std.string;
    import std.range;

    string menuLines(string[] choices)
    {
        string result;
        foreach (i, choice; choices)
        {
            result ~= format(" %s. %s\n", i + 1, choice);
        }
        return result;
    }

    string menu(string title,
        string[] choices,
        size_t width)
    {
        return format("%s\n%s\n%s",
            title.center(width),
            '='.repeat(width), // horizontal line
            menuLines(choices));
    }

    void main()
    {
        enum drinks =
            menu("Drinks",
                ["Coffee", "Tea", "Hot chocolate"], 20);
        writeln(drinks);
    }

Хотя один и тот же результат может быть достигнут разными способами, приведенная выше программа
выполняет нетривиальные операции, чтобы получить следующую строку:

.. code-block:: console

        Drinks
    ====================
    1. Coffee
    2. Tea
    3. Hot chocolate

Помните, что начальное значение констант перечисления, таких как drinks, должно быть известно во время
время компиляции. Этого факта достаточно, чтобы функция menu() была выполнена во время компиляции.
Значение, которое она возвращает во время компиляции, используется в качестве начального значения drinks. Как
В результате программа компилируется так, как если бы это значение было записано в явном виде в
программе:

.. code-block:: d

    // The equivalent of the code above:
    enum drinks = "       Drinks       \n"
                  "====================\n"
                  " 1. Coffee\n"
                  " 2. Tea\n"
                  " 3. Hot chocolate\n"

Чтобы функция выполнялась во время компиляции, она должна появиться в выражении
которое на самом деле необходимо во время компиляции:
* Инициализация статической переменной
* Инициализация переменной перечисления
* Вычисление длины массива фиксированной длины
* Вычисление аргумента значения шаблона
Очевидно, что невозможно выполнить каждую функцию во время компиляции. Например,
например, функция, обращающаяся к глобальной переменной, не может быть выполнена во время компиляции
потому что глобальная переменная начинает жить только во время выполнения. Поскольку stdout доступен только во время выполнения, функции, которые печатают, не могут быть
не могут быть выполнены во время компиляции.


**Переменная __ctfe**

Важным аспектом CTFE является то, что одна и та же функция используется как во время компиляции
и во время выполнения, в зависимости от того, когда нужен ее результат. Хотя функция
не должна быть написана специальным образом для CTFE, некоторые операции в функции
могут иметь смысл только во время компиляции или во время выполнения. Специальная переменная __ctfe
может быть использована для различения кода, который предназначен только для времени компиляции или только для
времени выполнения. Значение этой переменной равно true, если функция выполняется
для CTFE, и ложно в противном случае:

.. code-block:: d

    import std.stdio;

    size_t counter;
    int foo()
    {
        if (!__ctfe)
        {
            // This code is for execution at run time
            ++counter;
        }
        return 42;
    }

    void main()
    {
        enum i = foo();
        auto j = foo();
        writefln("foo is called %s times.", counter);
    }

Поскольку счетчик живет только во время выполнения, он не может быть увеличен во время компиляции. По
Поэтому приведенный выше код пытается увеличить его только во время выполнения.
Поскольку значение i определяется во время компиляции, а значение j
определяется во время выполнения, то сообщается, что foo() была вызвана всего один раз во время
во время выполнения программы:

.. code-block:: console

    foo is called 1 times

Резюме
------

* Тип возвращаемого значения функции auto выводится автоматически.
* Возвращаемое значение функции ref - это ссылка на существующую переменную.
* Возвращаемое значение функции auto ref - это ссылка, если это возможно, и копия
  в противном случае.
* inout переносит атрибут const, immutable или mutable параметра в
  возвращаемый тип.
* Чистая функция не может получить доступ к мутабельному глобальному или статическому состоянию. Компилятор
  определяет чистоту шаблонов, делегатов, анонимных функций и автофункций
  функций.
* Чистые функции не могут выбрасывать исключения. Компилятор определяет, является ли
  шаблон, делегат, анонимная функция или автофункция является no-throw.
* @nogc-функции не могут участвовать в операциях GC.
* @safe-функции не могут повреждать память. Компилятор определяет атрибуты безопасности
  атрибуты безопасности шаблонов, делегатов, анонимных функций и автофункций.
* @safe функции действительно безопасны, но не могут быть указаны как таковые; они считаются
  считаются @safe как программистом, так и компилятором.
  Функции ∙@system могут использовать все возможности D. @system - это атрибут безопасности по умолчанию
  атрибут безопасности по умолчанию.
* Функции могут выполняться и во время компиляции (CTFE). Это можно
  различать по значению специальной переменной __ctfe.
